EC2上に構築したGatlingサーバで、APIサーバからVPCピアリング接続でTiDB Cloudにクエリを実行してみた

EC2上に構築したGatlingサーバで、APIサーバからVPCピアリング接続でTiDB Cloudにクエリを実行してみた

Clock Icon2024.08.27

こんにちは、ゲームソリューション部のsoraです。
今回は、EC2上に構築したGatlingサーバで、APIサーバからVPCピアリング接続でTiDB Cloudにクエリを実行してみたことについて書いていきます。

構築する構成

構成は以下です。
sr-gatling-api-tidbcloud01
GatlingなどのインストールのためにNAT Gatewayを一時的に配置しています。
テストの結果を見るために、CloudWatch Logsへのログ出力をしています。
APIサーバで動作するソースコードやGatlingで使用するシナリオファイルをS3から取得するために、S3への接続ルートも作っています。
今回、GatlingサーバやAPIサーバの構築・設定、TiDB Cloudへのクエリ実行がメインのため、AWSリソースの構築自体は完了している状態から説明します。

TiDB Cloudの構築・設定

Dedicatedクラスタ作成

TiDB CloudにてDedicatedクラスタを作成します。
今回はテストのため、最小構成で構築します。
sr-gatling-api-tidbcloud02

VPCピアリング接続設定

VPCピアリングで接続するため、TiDB Cloudにてピアリング接続を作成します。
今回は割愛しますが、詳細は以下ブログの中で記載しております。
https://dev.classmethod.jp/articles/tidb-cloud-ec2-sysbench/

APIサーバの構築・設定

Node.jsのインストール

ユーザデータを使用してインストールします。
ベースは以下ブログに記載した通りですが、今回はnvmは不要なため少し変えています。
https://dev.classmethod.jp/articles/ec2-nvm-nodejs-userdata/

#!/bin/bash

# Update the package list and install necessary packages
dnf update -y

# タイムゾーンの設定
timedatectl set-timezone Asia/Tokyo

# rsyslogのインストール(システムログがjournalで保存され文字化けするため)
dnf install -y rsyslog
systemctl enable rsyslog
systemctl start rsyslog

# CloudWatch Agentのインストール
dnf install -y amazon-cloudwatch-agent
# Create the CloudWatch Agent configuration file for logging
bash -c 'cat <<EOL > /opt/aws/amazon-cloudwatch-agent/bin/config.json
{
  "logs": {
    "logs_collected": {
      "files": {
        "collect_list": [
          {
            "file_path": "/var/log/messages",
            "log_group_name": "/api-server",
            "log_stream_name": "{instance_id}",
            "retention_in_days": 7
          },
          {
            "file_path": "/var/log/api-app/*.log",
            "log_group_name": "/api-server-app",
            "log_stream_name": "{instance_id}",
            "retention_in_days": 7
          }
        ]
      }
    }
  }
}
EOL'

# Start the CloudWatch Agent
/opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a fetch-config -m ec2 -c file:/opt/aws/amazon-cloudwatch-agent/bin/config.json -s

# デフォルトでenableだが念のため明示的にenableしておく
systemctl enable amazon-cloudwatch-agent

# ログファイル用のディレクトリ作成
mkdir /var/log/api-app
chown -R ec2-user:ec2-user /var/log/api-app

# nodejsのインストールの準備
dnf install -y gcc-c++ make

# マルチパート形式
Content-Type: multipart/mixed; boundary="//"
MIME-Version: 1.0

--//
Content-Type: text/cloud-config; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="cloud-config.txt"

#cloud-config
cloud-init directives

--//
Content-Type: text/x-shellscript; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="userdata.txt"

#!/bin/bash
if ! su - ec2-user -c "command -v node" > /dev/null 2>&1; then
  su - ec2-user -c "curl -sL https://rpm.nodesource.com/setup_20.x | sudo -E bash -"
fi
--//--

# nodejsのインストール
dnf install -y nodejs

# ディレクトリ作成
mkdir /home/ec2-user/tidb-connection-api
chown -R ec2-user:ec2-user /home/ec2-user/tidb-connection-api

ソースコード

ソースコードは以下です。
フレームワークとしてExpressを使用し、ロガーにはpinoを使用します。
クエリが実行できることが見たいだけなので、TiDB Cloudクラスタ構築時のデフォルトのデータだけで実行可能なクエリにしています。

index.mjs
import express from 'express';
import mysql from 'mysql2/promise';
import dotenv from 'dotenv';
import fs from 'fs';
import pino from 'pino';

// ログファイルを指定
const logFile = fs.createWriteStream('/var/log/api-app/app.log', { flags: 'a' });
const logger = pino(logFile);

const app = express();

dotenv.config();

// データベース接続設定
const dbConfig = {
    host: process.env.TIDB_HOST,
    port: process.env.TIDB_PORT,
    user: process.env.TIDB_USER,
    password: process.env.TIDB_PASSWORD,
    database: process.env.TIDB_DATABASE,
    ssl: process.env.TIDB_ENABLE_SSL === 'true' ? {
        // SSLの設定
        minVersion: 'TLSv1.2',
        ca: fs.readFileSync(process.env.TIDB_CA_PATH)
    } : null,
};
// Gatlingから受けるポート
const PORT = 3000;

app.get('/read', async (req, res) => {
    let connection;
    try {
        // データベースに接続
        connection = await mysql.createConnection(dbConfig);
        logger.info('Connection has been established successfully.');
        // クエリを実行
        const [results, fields] = await connection.execute('SHOW DATABASES');
        // 結果を返す
        res.json({
            results: results,
            fields: fields
        });
        logger.info('Read Query OK');
    } catch (err) {
        logger.error('Error executing query:', err.message);
        res.status(500).json({ error: 'Database query failed' });
    } finally {
        if (connection) {
            // 接続を閉じる
            await connection.end();
        }
    }
});

app.use((req, res, next) => {
    logger.error('Not Found:', err.message);
    res.status(404).json({ message: 'Not Found' });
});

app.listen(PORT, () => {
    logger.info(`Server is running on port ${PORT}`);
});

TiDB Cloud接続用の.envファイルは以下です。
ホストやパスワードなど、環境に合わせて設定してください。
また、TiDB CloudのDedicatedクラスタ構築後にダウンロードしたca.cerも、S3経由で渡してjsファイルと同じ場所に配置しておきます。

TIDB_HOST=tidb.xxxxxxxxx.clusters.tidb-cloud.com
TIDB_PORT=4000
TIDB_USER=root
TIDB_PASSWORD=xxxxxxxx
TIDB_DATABASE=test
TIDB_ENABLE_SSL=true
TIDB_CA_PATH=ca.cer

Gatlingサーバの構築・設定

Gatlingのインストール

ユーザデータにてインストールします。
ユーザデータを使用しない場合のインストール方法は以下ブログに記載しています。
https://dev.classmethod.jp/articles/ec2-amazon-linux-2023-gatling/
上記ブログではGatling 3.9.5を使用していましたが、今回はGatling 3.11.5を使用します。
(テスト実行時に記載しますが、3.11以降で実行コマンドなどが若干異なっていたため注意が必要です。)

#!/bin/bash

# Update the package list and install necessary packages
dnf update -y

# タイムゾーンの設定
timedatectl set-timezone Asia/Tokyo

# JDKのインストール
dnf install -y java-21-amazon-corretto-devel

# Download and extract Gatling
GATLING_VERSION="3.11.5"
wget https://repo1.maven.org/maven2/io/gatling/highcharts/gatling-charts-highcharts-bundle/${GATLING_VERSION}/gatling-charts-highcharts-bundle-${GATLING_VERSION}.zip
unzip gatling-charts-highcharts-bundle-${GATLING_VERSION}.zip
cp -r gatling-charts-highcharts-bundle-${GATLING_VERSION} /home/ec2-user/gatling
chown -R ec2-user:ec2-user /home/ec2-user/gatling

テストシナリオ

テストシナリオは以下です。
テストシナリオはgatling/src/test/java/配下に配置します。
以下のシナリオでは10ユーザが接続するシナリオになっています。
{api-server-private-subnet}の部分は、APIサーバのプライベートサブネットのIPアドレスを指定します。

ApiSimulation.java
package simulation;

import io.gatling.javaapi.core.CoreDsl.*;
import io.gatling.javaapi.http.HttpDsl.*;
import io.gatling.javaapi.core.*;
import io.gatling.javaapi.http.*;

import java.time.Duration;

import static io.gatling.javaapi.core.CoreDsl.*;
import static io.gatling.javaapi.http.HttpDsl.*;

public class ApiSimulation extends Simulation {

    HttpProtocolBuilder httpProtocol = http
        .baseUrl("http://{api-server-private-subnet}:3000")
        .acceptHeader("application/json");

    ScenarioBuilder scn = scenario("TiDB Request Scenario")
        .exec(
            http("Request to API")
                .get("/read")
                .check(status().is(200))
        );

    {
        setUp(
            scn.injectOpen(atOnceUsers(10))
        ).protocols(httpProtocol);
    }
}

テスト実行

準備ができたためテストを実行します。
まずはAPIサーバを起動します。

node index.mjs

次にGatlingサーバにて準備したテストシナリオを実行します。

cd gatling
./mvnw gatling:test -Dgatling.simulationClass=simulations.ApiSimulation

ちなみにコマンドの説明は以下です。

  • ./mvnw:Maven Wrapperスクリプトを実行するコマンド(Windowsの場合はmvnw.cmd)
  • gatling:test:Gatlingのテスト実行
  • -Dgatling:JavaのシステムプロパティでGatlingを指定して実行する
  • simulationClass=simulations.ApiSimulation:シミュレーションクラスの指定

gatling:testの部分については、他には以下のようなオプションもあります。

  • gatling:test:実行(Mavenのテストの一部として実行)
    • CI/CDや定期的なテスト実行向き
  • gatling:execute:実行(testより柔軟性が高い、Mavenに依存しない)
    • 開発中のデバッグや特定のシミュレーション実行向き
  • gatling:compile:コンパイル
  • gatling:clean:実行結果のクリーンアップ
  • gatling:package:シミュレーションのパッケージング
  • gatling:verify:シミュレーション結果を検証

実行結果の確認

CloudWatch Logsを見てみると、APIサーバ側で指定した情報が取得できており、実行できていることが確認できました。
sr-gatling-api-tidbcloud03
TiDB Cloud側でメトリクスを確認してみると、クエリの実行ができていることが確認できました。
sr-gatling-api-tidbcloud04

参考

https://dtnavi.tcdigital.jp/dev_blog/devtool/負荷試験ツールのgatlingを使ってみたメモ/

最後に

今回は、EC2上に構築したGatlingサーバで、APIサーバからVPCピアリング接続でTiDB Cloudにクエリを実行してみたことを記事にしました。
どなたかの参考になると幸いです。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.